PHASE 1 · LINUX 网络栈实战

1.4Network Namespace

在一台机器上模拟多网段拓扑

0.3 那个双子网实验,你其实已经用了 network namespace(ip netns add h1…)——当时只是照着跑。这章把它讲透:namespace 到底是什么、隔离了什么、怎么用它在一台机器上搭出任意拓扑。讲完你会发现,这不只是个实验工具——它就是容器(Docker、k8s)实现网络隔离的同一套机制

一句话定义

network namespace(网络命名空间):Linux 的一种隔离机制,让一台机器里能存在多套互相独立的网络栈——各自的接口、IP、路由表、防火墙规则,互不可见。

1namespace 是什么:独立的网络栈

平时你整台机器共用一套网络栈:一张接口列表、一张路由表、一套 nftables 规则、一张 conntrack 表。network namespace 让你"复制"出多套——每套都是干净、独立的。一个 namespace 里,下面这些各有各的、彼此隔离:

主机 (default)
  • eth0 / wlan0 / lo / docker0…
  • 一张路由表
  • 一套 nftables
  • 一张 conntrack
  • 端口 80/443 已占
ns1
  • 只有 veth-a / lo
  • 独立路由表
  • 独立 nftables
  • 独立 conntrack
  • 端口空全空
ns2
  • 只有 veth-b / lo
  • 独立路由表
  • 独立 nftables
  • 独立 conntrack
  • 端口空全空

每个命名空间一套完全独立的网络栈,互不可见

包括:接口(ip link)、IP 地址(ip addr)、路由表(ip route)、策略路由(ip rule,1.2)、nftables / conntrack(1.3)、端口空间(两个 namespace 可各自有程序监听 8080 互不冲突),以及 /proc/net 下的一切。回想 0.3:为什么每个 namespace 都要单独 ip link set lo up?因为每个 namespace 有自己独立的 lo(1.1),默认 down。为什么要给每个单独配 IP、配路由?因为它们的网络栈彼此独立——现在这些都讲得通了。

类比:namespace 之于网络,像进程之于 CPU/内存

进程让每个程序以为自己独占一台机器;network namespace 让每套网络栈以为自己独占一台机器的网络。底层是同一个内核在做隔离视图。其实 namespace 是 Linux 的通用隔离机制,网络只是其中一种——还有 PID、mount、UTS 等 namespace,它们组合起来才拼出"容器"(第 4 节)。

2ip netns:操作命名空间

核心命令 0.3 全用过,这里系统列出:

bash
sudo ip netns add ns1            # 建一个命名空间
ip netns list                   # 列出
sudo ip netns exec ns1 <命令>    # 在 ns1 里跑命令(如 …exec ns1 ip addr)
sudo ip -n ns1        # ip 的简写:等价 ip netns exec ns1 ip …
sudo ip netns del ns1           # 删

关键:新建的 namespace 是"空"的——只有一个 down 的 lo,没有别的接口、没有路由、没有出口。要让它能通信,你得三步:

  1. 把接口放进去:物理接口可 ip link set eth0 netns ns1;更常见是造一对 veth(1.1),一端放进去;
  2. 在里面配 IP、配路由(用 ip -n ns1 addr/route);
  3. 把 lo 拉起来(ip -n ns1 link set lo up)。

把两个 namespace 连起来的标准做法,就是 1.1 讲的 veth pair:一对虚拟网线,两端分别 ip link set … netns,各进一个 namespace。多个 namespace 要连成网,就把 veth 一端插进一个公共 bridge(1.1)。

exec 的本质:只换网络视角

ip netns exec ns1 bash 会给你一个 shell,它的网络视角完全是 ns1 的——ip addr 只看到 ns1 的接口,curl 走的是 ns1 的路由。但它的文件系统、进程列表还是主机的:只有网络栈被换了。这就是"只隔离网络"的含义。

3重看 0.3 的双子网实验

回到 0.3 的拓扑:h1(10.0.1.0/24)— veth — r(路由器,两个网段)— veth — h2(10.0.2.0/24)。用现在学的东西,重新解读每一步:

  • ip netns add h1/h2/r —— 造三套独立网络栈(本章)。
  • ip link add … type veth … + ip link set … netns —— 用 veth pair(1.1)把命名空间两两连起来。
  • ip -n h1 addr add 10.0.1.2/24 dev v-h1 —— 在 h1 的独立栈里配地址,顺手生成直连路由(1.2 解开的那条 scope link)。
  • ip -n h1 route add default via 10.0.1.1 —— 在 h1 的独立路由表(本章)里加默认路由,让它知道出网段交给 r(0.3)。
  • ip netns exec r sysctl -w net.ipv4.ip_forward=1 —— 打开 r 的转发开关:主机默认不转发别人的包(0.6 / 1.3 的"主机 vs 路由器"分界)。
  • ip netns exec h1 ping 10.0.2.2 —— 在 h1 的网络视角里发 ping;回包 ttl=63 是因为过了 r 一跳(0.3)。

一句话:那个实验,就是用 namespace(独立栈)+ veth(连线)+ 配 IP/路由(1.2)+ 开转发,在一台机器上手搓了一个"两台主机 + 一台路由器"的真网络。

路由器关联 router-link

这正是你理解真实路由器的最佳沙盘:r 那个 namespace 就是一台最小路由器——两个接口各属一个网段、一张路由表、打开 ip_forward。Phase 3 的 3.1"最小路由器"会把它从 namespace 搬到真实双网口;在那之前,你可以在 namespace 里随便试任何路由器配置(加 NAT、加防火墙、加 VLAN),不怕搞坏真机。namespace 是练路由器的安全沙盒。

4namespace 与容器:Docker 的本质

1.1 提过:docker run 背后 = 建 namespace + 造 veth + 一端进容器、一端插 docker0(bridge)。现在你能看清全貌:

  • 容器 = 一组 namespace(network、PID、mount、UTS…)+ cgroups 的组合,让进程以为自己在一台独立机器里;
  • 其中 network namespace 给了它独立的接口、IP、路由、防火墙;
  • veth 把容器的 namespace 连到宿主机的 docker0 bridge,docker0 再经 NAT(1.3 的 masquerade!)让容器能上外网。

所以:Docker 的"容器网络",底层就是你这章手动玩的 namespace + veth + bridge + nftables NAT。你 Phase 0/1 一路手搓的东西,正是容器引擎自动替你做的事。

给"容器是什么"祛魅

容器不是虚拟机(没有独立内核),而是同一个内核用 namespace + cgroups 把进程"圈"起来,让它看到一个隔离的世界。网络这一面,就是 network namespace——你已经徒手造过它了。主机上 ip netns list 通常看不到 Docker 的命名空间(它不挂到 /var/run/netns),但 lsns -t netnsenter 能看到 / 进入容器的网络命名空间。

本章小结

  • network namespace = 一套独立隔离的网络栈(接口 / 地址 / 路由 / ip rule / nftables / conntrack / 端口 / proc-net 全独立)。
  • ip netns add/list/exec/del;ip -n NS 是 exec ip 的简写;新 namespace 是空的,要放接口(veth)、配 IP/路由、把 lo 拉起来。
  • 连接 namespace 用 veth pair(1.1),多个用公共 bridge;这就是 0.3 实验的全部机制。
  • r 那个 namespace 就是最小路由器;namespace 是练路由器配置的安全沙盒(铺向 3.1)。
  • 容器(Docker/k8s)的网络隔离,底层就是 network namespace + veth + bridge + nftables NAT——你手搓过的全套。

动手练习

  1. ip netns add test; ip -n test link:看新命名空间里只有一个 down 的 lo,印证"它是空的";ip netns del test 清理。
  2. 用两个 namespace + 一对 veth,搭一个最小的"两台直连主机"(不要路由器):各配同网段 IP、lo up,让它们互 ping。(这是 0.3 实验去掉路由器的最简版。)
  3. 思考题:为什么两个不同 namespace 里可以各跑一个监听 8080 的程序而不冲突?(回想"端口空间各自独立"。)
  4. 进阶:在 0.3 的三命名空间实验里,进 h1 的网络视角(ip netns exec h1 bash),跑 ip addrip routeping 10.0.2.2,体会"这个 shell 的网络完全是 h1 的";再 exit 回主机对比。